#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Lasso回归超参数调优专用脚本
Lasso Regression Hyperparameter Tuning Script

Author: ML Team
Date: 2025-11-16
Description: 专门用于Lasso回归的超参数调优
"""

import os
import sys
import json
import time
import numpy as np
import pandas as pd
from datetime import datetime
from concurrent.futures import ProcessPoolExecutor

# 添加项目根目录到路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))


class LassoHyperparameterTuner:
    """Lasso回归专用超参数调优器"""

    def __init__(self):
        """初始化Lasso调优器"""
        self.X_train, self.X_val, self.y_train, self.y_val = self.load_data()

        if self.X_train is None:
            raise ValueError("数据加载失败，无法初始化调优器")

        # 合并训练和验证数据用于交叉验证
        self.X_full = pd.concat([self.X_train, self.X_val])
        self.y_full = pd.concat([self.y_train, self.y_val])

        # 输出目录
        self.output_dir = os.path.join(os.path.dirname(__file__), "lasso_tuning")
        os.makedirs(self.output_dir, exist_ok=True)

        print("Lasso回归超参数调优器初始化完成")

    def load_data(self):
        """加载数据"""
        print("[INFO] 加载数据...")

        try:
            # 获取项目根目录 - 使用绝对路径
            script_file = os.path.abspath(__file__)
            model_dir = os.path.dirname(script_file)
            project_root = os.path.dirname(model_dir)

            # 加载训练数据
            train_path = os.path.join(project_root, 'feature', 'train_feature.csv')

            if not os.path.exists(train_path):
                print(f"[ERROR] 训练数据文件不存在: {train_path}")
                return None, None, None, None

            train_data = pd.read_csv(train_path)

            # 准备数据
            X = train_data.drop(['SalePrice', 'Id'], axis=1, errors='ignore')
            y = np.log1p(train_data['SalePrice'])  # 对数变换

            # 简单分割：80%训练，20%验证
            from sklearn.model_selection import train_test_split
            X_train, X_val, y_train, y_val = train_test_split(
                X, y, test_size=0.2, random_state=49
            )

            print(f"[INFO] 训练数据大小: {X_train.shape}")
            print(f"[INFO] 验证数据大小: {X_val.shape}")

            return X_train, X_val, y_train, y_val

        except Exception as e:
            print(f"[ERROR] 数据加载失败: {e}")
            return None, None, None, None

    def get_param_grid(self):
        """获取Lasso参数网格"""
        return {
            'alpha': [0.0001, 0.0003, 0.0005, 0.001, 0.003, 0.005, 0.01, 0.03, 0.05, 0.1],
            'max_iter': [2000, 3000, 5000, 8000],  # 增加迭代次数
            'tol': [1e-5, 1e-4, 5e-4, 1e-3],  # 更宽的容忍度范围
            'selection': ['cyclic', 'random'],
            'precompute': [False]  # 只使用False避免冲突
        }

    def get_random_params(self, n_iter=100):
        """生成随机参数组合"""
        param_grid = self.get_param_grid()
        params = []

        for _ in range(n_iter):
            param = {}
            for key, values in param_grid.items():
                param[key] = np.random.choice(values)
                # 转换numpy数据类型为Python原生类型
                if isinstance(param[key], (np.integer, np.floating, np.bool_)):
                    param[key] = param[key].item()
                elif isinstance(param[key], np.str_):
                    param[key] = str(param[key])
            params.append(param)

        return params

    def evaluate_single_params(self, params):
        """评估单组参数"""
        from sklearn.linear_model import Lasso
        from sklearn.model_selection import cross_val_score
        from sklearn.preprocessing import StandardScaler
        from sklearn.pipeline import Pipeline

        try:
            # 转换参数类型，确保是Python原生类型
            clean_params = {}
            for key, value in params.items():
                if isinstance(value, (np.integer, np.floating, np.bool_)):
                    clean_params[key] = value.item()
                else:
                    clean_params[key] = value

            # 创建管道
            pipeline = Pipeline([
                ('scaler', StandardScaler()),
                ('lasso', Lasso(**clean_params, random_state=49))
            ])

            # 5折交叉验证
            cv = 5
            rmse_scores = -cross_val_score(pipeline, self.X_full, self.y_full,
                                         cv=cv, scoring='neg_root_mean_squared_error')
            mae_scores = -cross_val_score(pipeline, self.X_full, self.y_full,
                                        cv=cv, scoring='neg_mean_absolute_error')
            r2_scores = cross_val_score(pipeline, self.X_full, self.y_full,
                                      cv=cv, scoring='r2')

            return {
                'params': params,
                'rmse_mean': rmse_scores.mean(),
                'rmse_std': rmse_scores.std(),
                'mae_mean': mae_scores.mean(),
                'mae_std': mae_scores.std(),
                'r2_mean': r2_scores.mean(),
                'r2_std': r2_scores.std(),
                'success': True
            }

        except Exception as e:
            print(f"参数评估失败: {params}, 错误: {e}")
            return {
                'params': params,
                'rmse_mean': float('inf'),
                'success': False,
                'error': str(e)
            }

    def grid_search(self, n_workers=2):
        """网格搜索"""
        print("开始Lasso网格搜索...")

        param_grid = self.get_param_grid()
        param_combinations = []

        from itertools import product
        keys = list(param_grid.keys())
        values = list(param_grid.values())

        for combination in product(*values):
            param_dict = dict(zip(keys, combination))
            param_combinations.append(param_dict)

        print(f"生成 {len(param_combinations)} 个参数组合")

        return self._parallel_evaluate(param_combinations, n_workers, "grid_search")

    def random_search(self, n_iter=80, n_workers=2):
        """随机搜索"""
        print(f"开始Lasso随机搜索，迭代次数: {n_iter}")

        param_combinations = self.get_random_params(n_iter)
        return self._parallel_evaluate(param_combinations, n_workers, "random_search")

    def _parallel_evaluate(self, param_combinations, n_workers, search_type):
        """并行评估参数组合"""
        all_results = []
        start_time = time.time()

        # 分批处理
        batch_size = 50
        total_batches = (len(param_combinations) + batch_size - 1) // batch_size

        for i in range(0, len(param_combinations), batch_size):
            batch = param_combinations[i:i + batch_size]
            batch_num = i // batch_size + 1

            print(f"处理批次 {batch_num}/{total_batches} ({len(batch)} 个参数组合)")

            # 并行执行
            with ProcessPoolExecutor(max_workers=n_workers) as executor:
                futures = [executor.submit(self.evaluate_single_params, params) for params in batch]

                for j, future in enumerate(futures):
                    try:
                        result = future.result()
                        all_results.append(result)

                        # 实时显示进度
                        if (j + 1) % 10 == 0:
                            successful_results = [r for r in all_results if r.get('success', False)]
                            if successful_results:
                                best_rmse = min(r['rmse_mean'] for r in successful_results)
                                print(f"  批次进度: {j+1}/{len(batch)}, 当前最佳RMSE: {best_rmse:.6f}")

                    except Exception as e:
                        print(f"  批次中第{j+1}个参数评估失败: {e}")

        total_time = time.time() - start_time
        successful_results = [r for r in all_results if r.get('success', False)]

        print(f"{search_type}完成，耗时: {total_time:.2f}秒")
        print(f"成功评估: {len(successful_results)}/{len(all_results)}")

        return self._analyze_results(all_results, search_type)

    def _analyze_results(self, results, search_type):
        """分析结果"""
        successful_results = [r for r in results if r.get('success', False)]

        if not successful_results:
            print("没有成功的参数评估结果!")
            return None

        # 按RMSE排序
        sorted_results = sorted(successful_results, key=lambda x: x['rmse_mean'])
        best_result = sorted_results[0]

        # 统计信息
        rmse_scores = [r['rmse_mean'] for r in successful_results]

        analysis = {
            'model_type': 'lasso',
            'search_type': search_type,
            'total_evaluations': len(results),
            'successful_evaluations': len(successful_results),
            'best_params': best_result['params'],
            'best_rmse': best_result['rmse_mean'],
            'best_rmse_std': best_result['rmse_std'],
            'best_mae': best_result['mae_mean'],
            'best_r2': best_result['r2_mean'],
            'rmse_statistics': {
                'mean': np.mean(rmse_scores),
                'std': np.std(rmse_scores),
                'min': np.min(rmse_scores),
                'max': np.max(rmse_scores)
            },
            'top_10_results': sorted_results[:10]
        }

        # 保存结果
        self._save_results(analysis, sorted_results)

        # 打印结果
        self._print_results(analysis)

        return analysis

    def _save_results(self, analysis, sorted_results):
        """保存结果"""
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

        # 保存详细结果
        results_file = os.path.join(self.output_dir, f"lasso_{analysis['search_type']}_results_{timestamp}.json")
        with open(results_file, 'w', encoding='utf-8') as f:
            json.dump({
                'analysis': analysis,
                'all_successful_results': sorted_results
            }, f, indent=2, ensure_ascii=False)

        # 保存CSV格式
        csv_data = []
        for i, result in enumerate(sorted_results[:20]):
            row = {
                'rank': i + 1,
                'rmse_mean': result['rmse_mean'],
                'rmse_std': result['rmse_std'],
                'mae_mean': result['mae_mean'],
                'r2_mean': result['r2_mean']
            }
            row.update(result['params'])
            csv_data.append(row)

        csv_df = pd.DataFrame(csv_data)
        csv_file = os.path.join(self.output_dir, f"lasso_{analysis['search_type']}_summary_{timestamp}.csv")
        csv_df.to_csv(csv_file, index=False, encoding='utf-8-sig')

        print(f"结果已保存:")
        print(f"  详细结果: {results_file}")
        print(f"  摘要结果: {csv_file}")

        # 更新模型配置
        self._update_model_config(analysis['best_params'])

    def _update_model_config(self, best_params):
        """更新模型配置文件"""
        config_path = os.path.join(os.path.dirname(__file__), "lasso", "model_config.json")

        if os.path.exists(config_path):
            with open(config_path, 'r', encoding='utf-8') as f:
                config = json.load(f)

            # 备份原始配置
            backup_path = config_path.replace('.json', f'_backup_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json')
            with open(backup_path, 'w', encoding='utf-8') as f:
                json.dump(config, f, indent=2, ensure_ascii=False)
            print(f"原始配置已备份到: {backup_path}")

            # 更新参数
            config['model_params'].update(best_params)

            # 保存新配置
            with open(config_path, 'w', encoding='utf-8') as f:
                json.dump(config, f, indent=2, ensure_ascii=False)

            print(f"模型配置已更新: {config_path}")

    def _print_results(self, analysis):
        """打印结果"""
        print("\n" + "="*60)
        print("Lasso回归超参数调优结果")
        print("="*60)
        print(f"搜索类型: {analysis['search_type']}")
        print(f"总评估次数: {analysis['total_evaluations']}")
        print(f"成功评估次数: {analysis['successful_evaluations']}")
        print(f"成功率: {analysis['successful_evaluations']/analysis['total_evaluations']*100:.1f}%")
        print()
        print("最佳结果:")
        print(f"  RMSE: {analysis['best_rmse']:.6f} ± {analysis['best_rmse_std']:.6f}")
        print(f"  MAE:  {analysis['best_mae']:.6f}")
        print(f"  R²:   {analysis['best_r2']:.4f}")
        print()
        print("最佳参数:")
        for key, value in analysis['best_params'].items():
            print(f"  {key}: {value}")
        print()
        print("RMSE统计:")
        print(f"  平均值: {analysis['rmse_statistics']['mean']:.6f}")
        print(f"  标准差: {analysis['rmse_statistics']['std']:.6f}")
        print(f"  最小值: {analysis['rmse_statistics']['min']:.6f}")
        print(f"  最大值: {analysis['rmse_statistics']['max']:.6f}")
        print("="*60)


def main():
    """主函数"""
    print("Lasso回归超参数调优")
    print("-" * 40)

    tuner = LassoHyperparameterTuner()

    # 选择搜索策略
    print("选择搜索策略:")
    print("1. 网格搜索 (全面但较慢)")
    print("2. 随机搜索 (快速但可能错过最优解)")

    try:
        choice = input("请选择 (1 或 2): ").strip()
        if choice == "1":
            print("\n开始网格搜索...")
            tuner.grid_search(n_workers=2)
        elif choice == "2":
            n_iter = input("请输入迭代次数 (默认80): ").strip()
            n_iter = int(n_iter) if n_iter.isdigit() else 80
            print(f"\n开始随机搜索，迭代次数: {n_iter}")
            tuner.random_search(n_iter=n_iter, n_workers=2)
        else:
            print("无效选择，使用默认随机搜索")
            tuner.random_search(n_iter=80, n_workers=2)
    except KeyboardInterrupt:
        print("\n调优被用户中断")
    except Exception as e:
        print(f"\n调优过程中出现错误: {e}")
        # 使用默认随机搜索
        print("使用默认随机搜索...")
        tuner.random_search(n_iter=80, n_workers=2)


if __name__ == "__main__":
    main()